#!/usr/bin/env python
########################################################################
#  Quote - On-line historical quotes
#  Copyright (C) 2007,8 Ofer Barkai
#  Inspired by pyq.py
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation version 2.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the 
# Free Software Foundation, Inc., 
# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
########################################################################
"""Caching of historical quotes and an interface to site specific modules."""


import shelve
import os
from lib import dirs
from lib.conf import logging
from lib.secid import validIsin
info = logging.info


CACHE = os.path.join(dirs.home('quote', True), 'cache')

def numDays(month, year):
    n = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)[month]
    if month == 2 and year % 4 == 0 and (year % 100 != 0 or year % 400 == 0):
        n += 1
    return n


def validDate(date):
    y, m, d = int(date[0:4]), int(date[4:6]), int(date[6:8])
    if m < 1 or m > 12:
        return False
    if d < 1 or d > numDays(m, y):
        return False
    return True


def nextDay(date):
    y, m, d = int(date[0:4]), int(date[4:6]), int(date[6:8])
    if (d < numDays(m, y)):
        d += 1
    else:
        d = 1
        if m < 12:
            m += 1
        else:
            m = 1
            y += 1
    return '%04d%02d%02d' % (y, m, d)


def prevDay(date):
    y, m, d = int(date[0:4]), int(date[4:6]), int(date[6:8])
    if d > 1:
        d -= 1
    else:
        if (m > 1):
            m -= 1
        else:
            m = 12
            y -= 1
        d = numDays(m, y)
    return '%04d%02d%02d' % (y, m, d)


def beforeFirst(date, count=7):
    for i in xrange(count):
        date = prevDay(date)
    return date


def adjusted2coef(result):
    def str2float(s):
        return float(s.replace(',', '')) # FIXME: assumes specific locale
    dates = sorted(result.keys())
    prevAdjusted = str2float(result[dates[0]]['adjusted'])
    prevClose = str2float(result[dates[0]]['close'])
    del result[dates[0]]
    del dates[0]
    for date in dates:
        adjusted = str2float(result[date].pop('adjusted'))
        close = str2float(result[date]['close'])
        result[date]['coef'] = \
            round(1.0 * prevAdjusted / adjusted * close / prevClose, 4)
        prevAdjusted = adjusted
        prevClose = close
    return result
    

def drange(d1, d2):
    current = d1
    if d1 <= d2:
        while current <= d2:
            yield current
            current = nextDay(current)
    else:
        while current >= d2:
            yield current
            current = prevDay(current)


def ranges(list, max):
    first = last = list[0]
    count = 1
    for date in list[1:]:
        if count >= max or date != nextDay(last):
            yield (first, last)
            first = last = date
            count = 1
        else:
            last = date
            count += 1
    yield (first, last)


def get(symbol, d1, d2, offline=False):
    if not validDate(d1) or not validDate(d2):
        raise Exception('dates are not valid')
    cache = shelve.open(CACHE, flag='c', writeback=True)
    source, sym = symbol.split(':')
    sym = sym.upper()
    symbol = ':'.join((source, sym))
    miss = []
    for date in drange(d1, d2):
        if date+symbol not in cache:
            miss.append(date)
    if miss and not offline:
        try:
            exec 'import '+source
        except ImportError:
            # added to make python -m quote.quote ... work
            exec 'from quote import '+source
        src = eval(source)
        for first, last in ranges(miss, 30):
            result = src.get(sym, first, last)
            if not result:
                continue
            dates = sorted(result.keys())
            for date in drange(dates[0], dates[-1]):
                if date in result:
                    cache[date+symbol] = result[date]
                else:
                    cache[date+symbol] = None
    result = {}
    for date in drange(d1, d2):
        key = date+symbol
        if key in cache:
            result[date] = cache[key]
    cache.close()
    return result


if __name__ == '__main__':
    from sys import argv, exit
    if len(argv) < 4 or len(argv) > 5 or \
       (len(argv) == 5 and argv[4] != 'True' and argv[4] != 'False'):
        print """Usage: quote.py symbol from-date to-date [ offline ]

symbol - <source>:<source-specific-symbol>
from-date, to-date - in YYYYMMDD format
offline - 'True' or 'False' (default is False)
"""
        exit(1)
    symbol, d1, d2 = argv[1:4]
    offline = False
    if len(argv) > 4 and argv[4] == 'True':
        offline = True
    result = get(argv[1], argv[2], argv[3], offline)
    print 'symbol', argv[1]
    for key in sorted(result.keys()):
        print 'date', key, 
        if result[key]:
            for field in sorted(result[key].keys()):
                print field, result[key][field],
        print

